package dao;

import java.util.List;

import javax.ejb.FinderException;
import javax.ejb.RemoveException;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import model.PersistentEntity;
import model.User;
import util.WebServiceException;
import util.WebServiceException.ExceptionType;

@Stateless
public class GenericDAO {

	public GenericDAO() {
		super();
	}

	/**
	 * 
	 * injected JPA entity manager
	 */
	@PersistenceContext
	protected EntityManager em;

	/**
	 * 
	 * creates entity
	 * 
	 * 
	 * 
	 * @param obj
	 * 
	 * @return
	 * 
	 * @throws WebServiceException
	 */

	public <T> void create(T object)
			throws WebServiceException {
		try {
			em.persist(object);
		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

	}

	/**
	 * 
	 * updates entity
	 * 
	 * 
	 * 
	 * @param obj
	 * 
	 * @return
	 * 
	 * @throws UpdateException
	 */

	public <T extends PersistentEntity> void update(T obj)
			throws WebServiceException {

		try {
			obj = em.merge(obj);
		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}
	}

	/**
	 * 
	 * refreshes entity(this action completely resets a state of entity)
	 * 
	 * @param obj
	 * 
	 * @throws DataStoreException
	 */

	public <T extends PersistentEntity> void refresh(T obj)
			throws WebServiceException {
		em.refresh(obj);
	}

	/**
	 * 
	 * removes entity but it refreshes it before.
	 * 
	 * Refreshing is required because entity could be detached/or composed out
	 * of transaction.
	 * 
	 * @param obj
	 * 
	 * @throws RemoveException
	 */

	public void remove(PersistentEntity obj) throws WebServiceException {

		try {
			em.refresh(obj);
			em.remove(obj);

		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

	}

	/**
	 * 
	 * checks if entityManager contains this entity
	 * 
	 * @param obj
	 * 
	 * @return
	 * 
	 * @throws DataStoreException
	 */

	public boolean contains(PersistentEntity obj) throws WebServiceException {
		try {
			return em.contains(obj);
		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

	}

	/**
	 * 
	 * detaches entity.
	 * 
	 * It's very useful when you need to apply some changes to entity within a
	 * transaction
	 * 
	 * but you don't want the container to call merge method because you want to
	 * perform a custom update to this entity.
	 * 
	 * @param obj
	 * 
	 * @throws DataStoreException
	 */

	public void detach(PersistentEntity obj) throws WebServiceException {

		try {
			if (em.contains(obj)) {
				em.detach(obj);
			}

		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

	}

	/**
	 * 
	 * finds entity ny PK
	 * 
	 * @param pk
	 * 
	 * @return
	 * 
	 * @throws FinderException
	 */

	public <T extends PersistentEntity> T findByPrimaryKey(
			Class<T> entityClass, Object primaryKey) throws WebServiceException {
		T obj = null;
		try {
			obj = em.find(entityClass, primaryKey);
		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

		if (obj == null) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

		return obj;

	}

	@SuppressWarnings("unchecked")
	public <T extends PersistentEntity> List<T> findAllPublic(Class<T> entityClass) {
		javax.persistence.criteria.CriteriaQuery cq = em.getCriteriaBuilder()
				.createQuery();
		cq.select(cq.from(entityClass));
		return em.createQuery(cq).getResultList();
	}
	
	@SuppressWarnings("unchecked")
	public <T extends PersistentEntity> List<T> findAllPrivate(Class<T> entityClass, User user) {
		javax.persistence.criteria.CriteriaQuery cq = em.getCriteriaBuilder()
				.createQuery();
		cq.select(cq.from(entityClass));
		// add restriction user in accessUsers
		//order by Date
		return em.createQuery(cq).getResultList();
	}

	@SuppressWarnings("unchecked")
	public <T extends PersistentEntity> List<T> findPage(Class<T> entityClass,
			int first, int max) {
		javax.persistence.criteria.CriteriaQuery cq = em.getCriteriaBuilder()
				.createQuery();
		cq.select(cq.from(entityClass));
		javax.persistence.Query q = em.createQuery(cq);
		q.setMaxResults(max);
		q.setFirstResult(first);
		return em.createQuery(cq).getResultList();
		
	}

	@SuppressWarnings("unchecked")
	public <T extends PersistentEntity> int count(Class<T> entityClass) {
		javax.persistence.criteria.CriteriaQuery cq = em.getCriteriaBuilder()
				.createQuery();
		cq.select(cq.from(entityClass));
		javax.persistence.Query q = em.createQuery(cq);
		return ((Long) q.getSingleResult()).intValue();

	}

	/**
	 * 
	 * executes named query with a single result. Hence if result is not found
	 * or
	 * 
	 * it contains multiply entries method throws FinderExpection.
	 * 
	 * @param namedQuery
	 * 
	 * @param params
	 * 
	 * @return
	 * 
	 * @throws FinderException
	 * 
	 * @throws DataStoreException
	 */

	public Object executeQuerySingleResult(String namedQuery,
			Object... params) throws WebServiceException {

		try {
			Query query = em.createNamedQuery(namedQuery);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					query.setParameter(i + 1, params[i]);
				}
			}
			List<?> list = query.getResultList();
			if (list.size() != 1 || list.get(0) == null) {
				throw new WebServiceException(ExceptionType.DATABASE_ERROR);
			}
			return list.get(0);

		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

	}

	/**
	 * 
	 * executes named query with multiply results
	 * 
	 * @param namedQuery
	 * 
	 * @param params
	 * 
	 * @return
	 * 
	 * @throws DataStoreException
	 */

	public List<?> executeQueryMultiplyResult(String namedQuery,
			Object... params) throws WebServiceException {
		try {
			Query query = em.createNamedQuery(namedQuery);

			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					query.setParameter(i + 1, params[i]);
				}
			}
			return query.getResultList();
		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

	}

	/**
	 * 
	 * executes named query with multiply results.
	 * 
	 * PersistentEntityhis method has startPosition and maxResult parameters.
	 * 
	 * @param namedQuery
	 * 
	 * @param startPosition
	 * 
	 * @param maxResult
	 * 
	 * @param params
	 * 
	 * @return
	 * 
	 * @throws DataStoreException
	 */

	protected List<?> executeQueryMultiplyResult(String namedQuery,

	Integer startPosition, Integer maxResult, Object... params)
			throws WebServiceException {
		try {
			Query query = em.createNamedQuery(namedQuery);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					query.setParameter(i + 1, params[i]);
				}
			}

			if (startPosition != null) {
				query.setFirstResult(startPosition);
			}

			if (maxResult != null) {
				query.setMaxResults(maxResult);
			}
			List<?> list = query.getResultList();
			return list;

		} catch (Exception e) {
			throw new WebServiceException(ExceptionType.DATABASE_ERROR);
		}

	}

}
